package com.xcity.game.account;

import java.util.Date;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xcity.Server;
import com.xcity.config.ConfigEx;
import com.xcity.db.entity.User;
import com.xcity.db.mapper.UserMapper;
import com.xcity.game.common.MessageConst;
import com.xcity.game.player.PlayerService;
import com.xcity.pomelo.PomeloRoute;
import com.xcity.pomelo.request.GetAccountRequest;
import com.xcity.pomelo.request.LoginRequest;
import com.xcity.pomelo.request.RegisterRequest;
import com.xcity.pomelo.response.HintResponse;
import com.xcity.pomelo.response.LoginResponse;
import com.xcity.pomelo.response.Response;
import com.xcity.util.DBUtils;
import com.xcity.util.DBUtils.MapperInvoker;
import com.xcity.util.PlatformApi;
import com.xcity.util.SessionUtils;

import naga.x.App;
import naga.x.event.Event;
import naga.x.event.EventListener;
import naga.x.event.Events;
import naga.x.net.Session;
import naga.x.net.annotation.OPHandler;
import naga.x.net.impl.pomelo.annotation.PomeloOP;
import naga.x.service.Service;
import naga.x.service.ServiceException;
import naga.x.util.KeywordsManager;
import naga.x.util.LRUCache;
import naga.x.util.StringUtils;
import naga.x.util.UUID;

@OPHandler
public class AccountService implements Service, EventListener {
	
	protected Map<String, User> accountsMap = new LRUCache<String, User>(256); // key: channelUid
	protected Map<Long, User> accountsMap2 = new LRUCache<Long, User>(256); // key: id
	protected KeywordsManager badwordsFilter;
	
	private static final Logger LOG = LoggerFactory.getLogger(AccountService.class);
	
	@Override
	public void startup() throws ServiceException {
		badwordsFilter = ((ConfigEx) App.getApp().getConfig()).getBadwordsFilter();
		App.getApp().getEventManager().register(this);
	}

	@Override
	public void shutdown() throws ServiceException {
	}

	@Override
	public String getId() {
		return getClass().getName();
	}
	
	@Override
	public int[] getEventTypes() {
		return new int[]{ Events.EVENT_SESSION_CLOSED };
	}

	@Override
	public void handleEvent(Event event) {
		switch (event.getType()) {
			case Events.EVENT_SESSION_CLOSED:
				Session sess = event.getParameter();
				Boolean kicked = (Boolean) sess.getAttribute(SessionUtils.KICKED);
				if (kicked != null && kicked) {
					return;
				}
				User acc = (User) sess.getAttribute(SessionUtils.ACCOUNT);
				if (acc != null) {
					removeAccount(acc);
				}
				break;
		}
	}

	@PomeloOP(PomeloRoute.LOGIN)
	protected void login(Session session, LoginRequest req) {
		User user = (User) session.getAttribute(SessionUtils.ACCOUNT);
		if (user != null) { // 已登录
			responseLogin(session, user, req);
			return;
		}
		String error = PlatformApi.verifyLogin(AccountChannel.forCode(req.getChannel()), req.getUserId(), req.getUserName(), req.getToken());
		if (error != null) {
			session.send(new HintResponse(req.getSerial(), error));
			return;
		}
		String cuid = req.getWrappedUserId(); // channel uid
		user = find(cuid);
		if (user != null) {
			responseLogin(session, user, req);
			return;
		}
		register(session, req);
	}
	
	void responseLogin(Session session, User user, RegisterRequest req) {
		user.setLastLoginTime(new Date());
		user.setLastLoginIp(session.getRemoteAddress());
		user.setToken(req.getToken());
		String cuid = user.getChannel().withoutPrefix(user.getChannelUid()); // 返回channel uid(需去掉前缀)
//		String token = CommonUtils.md5(uid + System.nanoTime());
		session.setAttribute(SessionUtils.ACCOUNT, user);
//		addAccount(user);
		session.send(new LoginResponse(req.getSerial(), req.getToken(), cuid, cuid, user.getNickname(), user.getLastLoginIp()));
		LOG.info("[ACCLOGIN]ID[{}]NAME[{}]CHANNEL[{}]CID[{}]", user.getId(), user.getName(), user.getChannel(), user.getChannelUid());
	}
	
	@PomeloOP(PomeloRoute.REGISTER)
	protected void register(Session session, RegisterRequest req) {
		if (Server.getInstance().hasState(Server.STATE_REGISTER_BAN_MASK)) {
			session.send(new HintResponse(req.getSerial(), MessageConst.REGISTER_CLOSED));
			return;
		}
		String uname = req.getUserName();
		if (uname == null || (uname = uname.trim()).isEmpty()) {
//			session.send(new HintResponse(req.getSerial(), MessageConst.ACCOUNT_NAME_EMPTY));
//			return;
			uname = MessageConst.ACCOUNT_VISITOR;
		}
		if (uname.length() > PlayerService.NAME_MAX_LEN) {
//			session.send(new HintResponse(req.getSerial(), MessageFormat.format(MessageConst.PLAYER_NAME_LEN_EXCEED, PlayerService.NAME_MAX_LEN)));
//			return;
			uname = uname.substring(0, PlayerService.NAME_MAX_LEN);
		}
//		if (badwordsFilter != null && badwordsFilter.containsBadWord(uname)) {
//			session.send(new HintResponse(req.getSerial(), MessageConst.ACCOUNT_NAME_INVALID));
//			return;
//		}
		String ip = session.getRemoteAddress();
		String cuid = req.getWrappedUserId();
		User user = new User(uname, User.DEAFULT_PASSWORD, ip, null);
		AccountChannel ac = AccountChannel.forCode(req.getChannel());
		if (StringUtils.isEmpty(req.getUserId()) // the orginal id
//				|| ac == AccountChannel.OFFICIAL
				|| ac == AccountChannel.UNKNOWN) {
			ac = AccountChannel.OFFICIAL;
			user.setChannelUid(ac.withPrefix(UUID.randomUUID().toString()));
		} else {
			user.setChannelUid(cuid);
		}
		user.setChannel(ac);
		DBUtils.save(user, UserMapper.class);
		addAccount(user);
		LOG.info("[ACCREG]ID[{}]NAME[{}]CHANNEL[{}]PLATFORM[{}]CID[{}]", user.getId(), user.getName(), ac, req.getPlatform(), user.getChannelUid());
		responseLogin(session, user, req);
	}
	
	public User find(String channelUid) {
		User acc = accountsMap.get(channelUid);
		if (acc == null) {
			acc = DBUtils.invoke(UserMapper.class, new MapperInvoker<User, UserMapper>() {
				@SuppressWarnings("unchecked")
				@Override
				public User invoke(UserMapper mapper) {
					User acc = mapper.findByChannelUid(channelUid);
					if (acc != null) {
						List<Long> pids = mapper.getAllPlayerIds(acc.getId());
						acc.setPlayerIds(pids);
					}
					return acc;
				}
			});
			if (acc != null) {
				addAccount(acc);
			}
		}
		return acc;
	}
	
	public User findById(final long id) {
		User acc = accountsMap2.get(id);
		if (acc == null) {
			acc = DBUtils.invoke(UserMapper.class, new MapperInvoker<User, UserMapper>() {
				@SuppressWarnings("unchecked")
				@Override
				public User invoke(UserMapper mapper) {
					User acc = mapper.find(id);
					if (acc != null) {
						List<Long> pids = mapper.getAllPlayerIds(acc.getId());
						acc.setPlayerIds(pids);
					}
					return acc;
				}
			});
			if (acc != null) {
				addAccount(acc);
			}
		}
		return acc;
	}
	
	protected void addAccount(User user) {
		accountsMap.put(user.getChannelUid(), user);
		accountsMap2.put(user.getId(), user);
	}
	
	protected void removeAccount(User user) {
		accountsMap.remove(user.getChannelUid());
		accountsMap2.remove(user.getId());
	}
	
	@PomeloOP(PomeloRoute.GET_ACCOUNT_INFO)
	protected void requestAccountInfo(Session sessioin, GetAccountRequest req) {
		sessioin.send(new Response(req.getSerial()));
	}

}
